package com.example.ormsqlsplicingframe1.session;

import com.example.ormsqlsplicingframe1.filter.SqlLogFilter;
import com.example.ormsqlsplicingframe1.sql.*;
import com.example.ormsqlsplicingframe1.utlis.NodeSqlUtil;
import com.example.ormsqlsplicingframe1.utlis.ProxySessionUtils;
import com.example.ormsqlsplicingframe1.utlis.ProxyTransactionListSessionUtils;
import lombok.SneakyThrows;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;

import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.Collectors;

public abstract class BaseSession implements EntitySession{


    private  DataSource datasource;

    private Map<String,DataSource> dataSourceMap;

    public void setDatasource(Map<String, DataSource> dataSourceMap) {
        this.dataSourceMap = dataSourceMap;
    }

    public void setDatasource(String name, DataSource dataSource) {
        if (dataSourceMap == null) dataSourceMap = new HashMap<>();
        dataSourceMap.put(name,dataSource);
    }

    private static ThreadLocal<Boolean> springTc = ThreadLocal.withInitial(
            () -> false
    );



    public BaseSession(DataSource datasource) {
        this.datasource = datasource;
    }


    List<SqlFilter> filters = new ArrayList<>();

    private SqlLogFilter  log = new SqlLogFilter();

    private static ThreadLocal<LongAdder> threadLocal = ThreadLocal.withInitial(() -> new LongAdder());

    @SneakyThrows
    @Override
    public void startTransaction(boolean spring) {

        if(threadLocal.get().intValue() == 0){
            springTc.set(spring);

            if (!spring){
                getConnection().setAutoCommit(false);
            }

        }
        threadLocal.get().add(1);
        ProxySessionUtils.route().startTransaction();
    }

    @SneakyThrows
    @Override
    public void commitTransaction() {

        if (threadLocal.get().intValue() == 0){
            return;
        }

        threadLocal.get().add(-1);
        ProxySessionUtils.route().commitTransaction();
        if(threadLocal.get().intValue() == 0){

            try {
                if(springTc.get() == false){
                    getConnection().commit();
                }
            }finally {
                springTc.set(false);
            }

        }
    }







    @SneakyThrows
    @Override
    public void rollbackTransaction() {

        if (threadLocal.get().intValue() == 0){
            return;
        }
        threadLocal.get().add(-1);
        ProxySessionUtils.route().rollbackTransaction();
        if(threadLocal.get().intValue() == 0){

            try {
                if (springTc.get() == false){
                    getConnection().rollback();
                }
            }finally {
                springTc.set(false);
            }
        }


    }

    @Override
    public boolean isOpenTransaction() {
        if (threadLocal.get().intValue() >0) return true;
        return false;
    }

    protected void before(Sql sql){

        log.before(sql);
        for (SqlFilter filter : filters) {
            filter.before(sql);
        }
    }

    protected void before(Sql sql,String select){

        log.before(select,sql);
        for (SqlFilter filter : filters) {
            filter.before(sql);
        }
    }


    protected Object after(Sql sql,Object data){

        for (SqlFilter filter : filters) {
           data =  filter.after(sql,data);
        }

        return data;
    }

    @Override
    public int insert(InsertSql sql) {
        if (!NodeSqlUtil.getAddBatch()){
            before(sql);
        }

        return updateCarryOut(sql);
    }


    public int insert(boolean isLog) {

        if (isLog){
            NodeSqlUtil.getNodeSql().forEach(row ->{
                before(row.getInsert());
            });
        }
        int i = insertCarryOut(NodeSqlUtil.getNodeSql().get(0).getInsert(), NodeSqlUtil.getNodeSql());
        NodeSqlUtil.clear();
        return i;
    }

    @Override
    public int update(UpdateSql sql) {
        before(sql);
        return updateCarryOut(sql);
    }

    @Override
    public int delete(DeleteSql sql) {
        before(sql);
        return updateCarryOut(sql);
    }

    @Override
    public <E> List<E> select(SelectSql sql, Class<E> clazz) {

        before(sql);
        //before
        //sql执行
        List<E> val =  selectList(sql,clazz);

        //after
        after(sql,val);

        return val;
    }

    @Override
    public <E> E selectOnt(SelectSql sql, Class<E> clazz) {
        E val = select(sql, clazz).get(0);
        return val;
    }

    @SneakyThrows
    @Override
    public long count(SelectSql sql) {

        String select ="select";
        String from = "from";
        String limit = "limit";

        String total = "total";

        StringBuilder sb = new StringBuilder(sql.getSql());
        int i = sb.indexOf(from);
        sb.replace(select.length(),i-1," count(*) as total");

        int indexOf = sb.indexOf(limit);

        String s = sb.substring(0,indexOf);

        before(sql,s);
        Connection connection = null;
        try {
            connection = getConnection();

            ArrayList<Map<String, String>> list = selectListCont(connection, s, sql);
            String totalTow ="";
            for (Map<String, String> map : list) {
                totalTow = map.get(total);
                if (totalTow != null && !totalTow.equals("")){
                    continue;
                }
            }
            int anInt = Integer.parseInt(totalTow);
            return anInt;
        }finally {
            connection.close();
        }
    }


    protected Object transaction(Callable callable){
        startTransaction();
        try {
            Object val = callable.call();
            return val;
        }catch (Exception e){
            rollbackTransaction();
            throw new RuntimeException(e);
        }


    }


    private String toggleData;


    public void toggleData(String dataName){
        this.toggleData = dataName;
    }

    @SneakyThrows
    private Connection getConnection()  {
        DataSource source = null;
        if (toggleData != null){
             source = dataSourceMap.get(toggleData);
             this.toggleData = null;
            if (source == null){
                throw new RuntimeException("当前数据源不存在");
            }
        }else {
            source = datasource;
        }

        Connection connection =  DataSourceUtils.getConnection(source);
        if(threadLocal.get().intValue() == 0){
            connection.setAutoCommit(true);
        }
        return connection;
    }

    @SneakyThrows
    protected  int updateCarryOut(Connection connection,Sql sql){

        PreparedStatement statement = null;

        try {
            statement = connection.prepareStatement(sql.getSql());

            for (int i = 0; i < sql.getValues().size(); i++) {
                statement.setObject(i+1,sql.getValues().get(i));
            }

            //是否开启事务
            int i = statement.executeUpdate();

            if (i >0){
                return 1;
            }
            return 0;
        }finally {
            if (statement != null) statement.close();
        }

    }

    @SneakyThrows
    protected  int updateCarryOut(Connection connection,Sql sql,List<NodeSql> list){

        PreparedStatement statement = null;

        try {
            statement = connection.prepareStatement(sql.getSql());


            for (NodeSql nodeSql : list) {
                int index = 1;
                for (Object o : nodeSql.getList()) {
                    statement.setObject(index, o);
                    index++;
                }
                statement.addBatch();
            }


            //是否开启事务
            int[] i = statement.executeBatch();

            if (i.length >0){
                return 1;
            }
            return 0;
        }finally {
            if (statement != null) statement.close();
        }

    }

    @SneakyThrows
    protected  int updateCarryOut(Sql sql){

        Connection connection = null;

        try {
            connection = getConnection();
            return updateCarryOut(connection,sql);
        }finally {
            if (connection != null) connection.close();
        }
    }

    @SneakyThrows
    protected  int insertCarryOut(Sql sql,List<NodeSql> list){

        Connection connection = null;

        try {
            connection = getConnection();
            return updateCarryOut(connection,sql,list);
        }finally {
            if (connection != null) connection.close();
        }
    }


    @SneakyThrows
    private  <E> List<E> selectList(SelectSql sql, Class<E> clazz) {

        Connection connection = null;
        try {
            connection = getConnection();

            return selectList(connection, sql, clazz);
        }finally {
        }
    }

    @SneakyThrows
    private  <E> List<E> selectList(Connection connection, SelectSql sql, Class<E> clazz){
        PreparedStatement statement = null;

        ResultSet resultSet = null;
        try {

            statement = connection.prepareStatement(sql.getSql());
            for (int i = 0; i < sql.getValues().size(); i++) {

                Object value = sql.getValues().get(i);

                value = encode(value);

                statement.setObject(i+1,value);
            }
            resultSet = statement.executeQuery();

            ResultSetMetaData metaData = resultSet.getMetaData();

            List<E> list  = new ArrayList<>();
            while (resultSet.next()) {
                E e = clazz.newInstance();
                for (int i = 1; i <= metaData.getColumnCount(); i++) {

                    String label = metaData.getColumnLabel(i);
                    String value = resultSet.getString(label);

                    if (value != null){
                        getJaveObj(label,value,e);
                    }
                }
                list.add(e);
            }
            return list;
        }finally {
            if (statement != null) statement.close();
            if (resultSet != null) resultSet.close();
        }



    }

    @SneakyThrows
    private  <E> E getJaveObj(String label, String value, E e){

        Map<String, Field> fieldMap = Arrays.stream(e.getClass().getDeclaredFields()).distinct().collect(Collectors.toMap(row -> row.getName(), row -> row));

        if (fieldMap.get(label) == null) {
            getJaveObj(label, value, e,e.getClass().getSuperclass());
        }else {
            Field field = fieldMap.get(label);
            field.setAccessible(true);

            Object obj =  decode(value,field);
            field.set(e,obj);
        }
        return null;
    }

    @SneakyThrows
    protected <E> E getJaveObj(String label, String value, Object e,Class<?> eClass){
        Map<String, Field> fieldMap = Arrays.stream(eClass.getDeclaredFields()).distinct().collect(Collectors.toMap(row -> row.getName(), row -> row));

        if (fieldMap.get(label) == null) {
            getJaveObj(label, value,e, e.getClass().getSuperclass());
        }else {
            Field field = fieldMap.get(label);
            field.setAccessible(true);

            Object obj =  decode(value,field);
            field.set(e,obj);
        }
        return null;
    }

    protected  abstract   Object decode(String value, Field field);

    protected abstract Object encode(Object value);


    @SneakyThrows
    private  ArrayList<Map<String, String>> selectListCont(Connection connection,String selectSql, SelectSql sql){
        PreparedStatement statement = null;

        ResultSet resultSet = null;
        try {
            statement = connection.prepareStatement(selectSql);
            for (int i = 0; i < sql.getValues().size(); i++) {

                Object value = sql.getValues().get(i);

                value = encode(value);

                statement.setObject(i+1,value);
            }
            ArrayList<Map<String, String>> arrayList = new ArrayList<>();
            resultSet = statement.executeQuery();

            ResultSetMetaData metaData = resultSet.getMetaData();

            while (resultSet.next()) {
                HashMap<String, String> map = new HashMap<>();
                for (int i = 1; i <= metaData.getColumnCount(); i++) {

                    String label = metaData.getColumnLabel(i);
                    String value = resultSet.getString(label);

                    map.put(label,value);
                }
                arrayList.add(map);
            }
            return arrayList;
        }finally {
            if (statement != null) statement.close();
            if (resultSet != null) resultSet.close();
        }
    }

}
